# 项目：python_project
# 文件：tools
# 作者：WuTong
# 日期：2025/5/27,10:35
# coding:utf-8
import time
from functools import wraps
from sys import version_info

import yaml, os

from selenium import webdriver


def excepter(cm):
    """
    异常处理装饰器，在每个可能存在异常的方法中都可以使用，
    如果出现异常会返回None并将异常信息保存在日志中
    :param cm: classname.methodname
    :return: object
    """
    def except_execute(func):
        @wraps(func)  # 保存原有函数或方法的函数名和文档字符串
        def except_handle(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                LogUtil.write_log(cm, str(e))
                return None
        return except_handle
    return except_execute

class UITool:

    driver = None

    @classmethod
    def get_driver(cls):
        browser = FileUtil.read_json('conf/base.json')['browser']
        if cls.driver is None:
            cls.driver = getattr(webdriver, browser)()
            cls.driver.implicitly_wait(5)
            cls.driver.maximize_window()
        return cls.driver

    @classmethod
    def screenshot(cls,testversion):
        images_dir = CommonUtil.get_dir('images')
        CommonUtil.create_dir(images_dir)
        version_dir = f'{images_dir}\\{testversion}'
        CommonUtil.create_dir(version_dir)
        temp_time = CommonUtil.get_ctime()
        stime = temp_time.replace(' ', '_').replace(':', '')
        images_name = f'{stime}.png'
        images_path = os.path.join(version_dir,images_name)
        cls.driver.get_screenshot_as_file(images_path)
        return images_path

class FileUtil:

    @classmethod
    def read_json(cls, path):
        import json5
        with open(path, encoding='utf8') as file:
            contents = json5.load(file)
        return contents

    @classmethod
    def read_csv(cls, path):
        with open(path, encoding='utf8') as file:
            contents = file.readlines()
        li = []
        for content in contents:
            if not content.startswith('# ') and content != '\n':
                if '# ' in content.strip():
                    item = content.strip().split('# ')[0].strip()
                else:
                    item = content.strip()
                li.append(item)
        return li

    @classmethod
    def read_yml(cls, path):

        with open(path, "r") as file:
            contents = list(yaml.safe_load_all(file.read()))
        return contents

    @classmethod
    def write_yaml(cls, item, path):
        # 先读取源文件，再添加内容，之后统一写入
        filename = os.path.join(os.path.dirname(__file__), path)  # 获取yaml文件的绝对路径
        contents = cls.read_yml(filename)
        for c in item:
            contents.append(c)

        with open(filename, 'w', encoding='utf8') as file:
            yaml.safe_dump_all(contents, file, allow_unicode=True)

class CommonUtil:

    @classmethod
    def create_dir(cls,path):
        if not os.path.exists(path):
            os.mkdir(path)

    @classmethod
    def sub_str(cls, msg1, msg2):
        """
        进行两个字符串的相减，返回不一样的部分
        :param msg1: 长的字符串
        :param msg2: 短的字符串
        :return: 字符串的差
        """
        return msg1[len(msg2):]

    @classmethod
    def get_ctime(cls):
        import time
        return time.strftime('%Y%m%d %H:%M:%S', time.localtime())

    @classmethod
    def get_dir(cls,dirname):
        """
        获取根包目录的绝对路径和名称
        :return:
        """
        import sys
        cabs = sys.path[0]  # 当前文件的绝对路径
        babs = sys.path[1]  # 工程的绝对路径
        path = CommonUtil.sub_str(cabs, babs)
        root_name = str(path).split('\\')[1]  # 获取根包名
        dir_path = f'{babs}\\{root_name}\\{dirname}'
        return dir_path

class LogUtil:

    @classmethod
    def get_log_name(cls):
        """
        获取日志文件的全名
        :return: 带路径的日志文件全名
        """
        temp_time = CommonUtil.get_ctime()
        # stime = temp_time.replace(' ', '_').replace(':','')
        stime = temp_time.split(' ')[0]
        log_name = f'tf_{stime}.log'
        logs_path = CommonUtil.get_dir('log')
        return os.path.join(logs_path, log_name)

    @classmethod
    def write_log(cls, cm, content):
        log_dir = CommonUtil.get_dir('log')
        if not os.path.isdir(log_dir):  # 判断logs目录是否存在，如果不在则创建它
            os.mkdir(log_dir)
        record = f'{CommonUtil.get_ctime()} ======= {cm} ======== {content}\n'
        log_full_name = cls.get_log_name()
        if os.path.exists(log_full_name):
            with open(log_full_name, 'a', encoding='utf8') as file:
                file.write(record)
        else:
            with open(log_full_name, 'w', encoding='utf8') as file:
                file.write(record)

class DBUtil:

    @classmethod
    def get_conn(cls, dbname):
        import pymysql
        db_conn_info = FileUtil.read_json('conf/base.json')[dbname]
        return pymysql.connect(**db_conn_info, charset='utf8')

    @classmethod
    def query(cls, dbname, sql):
        conn = cls.get_conn(dbname)
        cur = conn.cursor()
        cur.execute(sql)
        result = cur.fetchall()
        cur.close()
        conn.close()
        return result

    @classmethod
    def update(cls,dbname, sql):
        conn = cls.get_conn(dbname)
        cur = conn.cursor()
        cur.execute(sql)
        conn.commit()
        cur.close()
        conn.close()

class Assert:

    @classmethod
    def get_fail_resul(cls,ttype,testversion):
        tresult = 'test fail'
        if ttype == 'ui':
            image_path = UITool.screenshot(testversion)
        else:
            image_path = ''
        return image_path, tresult

    @classmethod
    def get_result(cls,by, info, actual, testversion):
        """

        :param by:
        :param info:
        :param actual:
        :param testversion:
        :return:
        """
        ttype = info['ttype']
        if by == 'equal':  # 如果执行的是断言相等
            if info['expect'] == actual:
                tresult = 'test pass'
                image_path = ''
            else:
                image_path, tresult = cls.get_fail_resul(ttype, testversion)  # 断言失败之后获取测试结果和截图路径
        elif by == 'not equal':  # 如果执行的是断言不相等
            if info['expect'] != actual:
                tresult = 'test pass'
                image_path = ''
            else:
                image_path, tresult = cls.get_fail_resul(ttype, testversion)
        elif by == 'contain':
            if info['expect'] in actual:
                tresult = 'test pass'
                image_path = ''
            else:
                image_path, tresult = cls.get_fail_resul(ttype, testversion)
        else:
            tresult = 'test pass'
            image_path = ''
        return image_path, tresult

    @classmethod
    def get_userid(cls,conf):
        tname = conf['tname']
        get_tid_sql = f'select tid from tester where tname="{tname}"'
        return DBUtil.query('appdb', get_tid_sql)[0][0]

    @classmethod
    def write_result_db(cls,by,info,actual):
        conf = FileUtil.read_json('conf/base.json')
        dbname = conf['appdb']['database']
        ttype = info['ttype']  # 测试类型
        testversion = conf['testversion']  # 测试版本
        image_path, tresult = cls.get_result(by, info, actual, testversion)  # 截图路径和测试结果
        tid = cls.get_userid(conf)  # 用户id
        caseid = info['caseid']  # 用例id
        casetitle = info['casetitle']  # 用例标题
        casestep = info['casestep']  # 测试步骤
        casedata = info['casedata']  # 测试数据
        expect = info['expect']  # 期望结果
        caselevel = info['caselevel']  # 用例等级

        insert_sql = (f'insert into ydbossresult(testversion,'
                      f'tid,ttype,caseid,casetitle,casestep,casedata,'
                      f'expect,actual,imagepath,tresult,caselevel) '
                      f'values("{testversion}",{tid},"{ttype}","{caseid}",'
                      f'"{casetitle}","{casestep}","{casedata}","{expect}",'
                      f'"{actual}","{image_path}","{tresult}","{caselevel}")')
        DBUtil.update(dbname, insert_sql)


    @classmethod
    def assert_equal(cls, info, actual):
        cls.write_result_db('equal',info,actual)

    @classmethod
    def assert_not_equal(cls, info, actual):
        cls.write_result_db('not equal', info, actual)

    @classmethod
    def assert_contain(cls,info, actual):
        cls.write_result_db('contain', info, actual)

class Report:
    # 1.在根包下生成report目录；2.定义测试报告的名称:report_年月日时分秒_v1.1.html,和前面的report路径进行拼接;
    # 3.读取template.html成字符串；4.生成相应的如时间、版本等信息，同时通过sql查询获取所需要展示的数据；
    # 5.在$test-result这个位置使用for循环生成html字符串，即<tr><td>循环结构；之后会拼接成一个大的字符串，替换原有的$;
    # 6.通过with open向测试报告写入这个字符串
    # 7.通过浏览器打开报告，就可以看到具体的报告数据。
    @classmethod
    def get_report_name(cls):
        temp_time = CommonUtil.get_ctime()
        stime = temp_time.split(' ')[0]
        report_name = f'report_{stime}_v1.1.html'
        report_path = CommonUtil.get_dir('report')
        return os.path.join(report_path, report_name)

    @classmethod
    def read_html(cls):
        with open('template.html','r',encoding='utf8') as file:
            template = file.read()
        return template

    @classmethod
    def get_case_data(cls):
        query_sql = 'select casedata from ydbossresult'
        case_data = DBUtil.query('appdb',query_sql)
        return case_data

    @classmethod
    def get_html_data(cls,template,case_data):
        time_info = CommonUtil.get_ctime()
        version_info = 'v1.1'
        html_info = template.replace('$last-time',time_info).replace('$test-version',version_info)
        html_str = ""
        for data in case_data:
            data_row = "<tr>"
            for info in data:
                data_row += f"<td>{info}</td>"
            data_row += "</tr>"
            html_str +=data_row
        html_info = html_info.replace('$test-result',html_str)
        return html_info

    @classmethod
    def write_report(cls,html_info):
        report_path = cls.get_report_name()
        with open(report_path,'w',encoding='utf8') as file:
            file.write(html_info)

    @classmethod
    def open_report(cls):
        report_path = cls.get_report_name()
        import webbrowser
        webbrowser.open(f"file://{os.path.abspath(report_path)}")

if __name__ == '__main__':

    # driver = UITool.get_driver()
    # driver.get('https://www.baidu.com')
    # time.sleep(2)
    # UITool.screenshot('v11')
    # info = {"ttype":'ui',"caseid":"emp_add_004","casetitle":"输入合法数据，验证添加员工成功",
    #         "casestep":"1.aaa;2.bbb;3.cccc","casedata":"{'uname':'zsf','upass':'123'}",
    #         "expect":"add pass", "caselevel":"2"}
    #
    # Assert.assert_contain(info, "add pass1")
    print(Report.read_html())
